﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Threading;
using System.Threading;
using System.Net.Sockets;
using System.Windows.Media.Effects;
using System.Media;
using System.IO;
using FCS.Communication;
using FCS.GlobalComponents;

namespace FCS.Kiosk
{
    public partial class FountainControl : UserControl
    {
        FountainConnection.CPQClient _connection;
        List<string> _activeValves = new List<string>();
        List<string> _restrictedValves = new List<string>();
        bool _connectionLost = false;
        bool _restrictFileFound = true;
        bool _codeFileFound = true;
        EventLogWriter _eventLogWriter = new EventLogWriter("Kiosk");
        string _code;
        bool _isUserInteraction = false;
        IDictionary<string, Valve> _valves = new Dictionary<string, Valve>();


        const int TIMEOUT = 30000; // Let user interact for 30 seconds
        const int TRYOUT = 3; // Number of times to try a reconnection before informing the user of an error

        object _monitor = new object();

        /// <summary>
        /// Connects the kiosk to the server and sets up the the main tab
        /// </summary>
        public FountainControl()
        {
            InitializeComponent();
            CreateDots();
            ReadCode();

#if Release
            // hide the cursor when deployed
            this.Cursor = Cursors.None;
#endif
            try
            {
                Dispatcher.Invoke(new MethodDelegate(delegate()
            {

                _connection = new FountainConnection.CPQClient();
                _connection.SetDefaultCRIOAddress(_code);
            }));
                SendRestrictedValves();
                UpdateStateMessage(RobustResourceStatus.Idling);

                Thread t = new Thread(() => { GetCurrentFountainState(); });
                t.IsBackground = true;
                t.Start();
            }
            catch (Exception ex)
            {
                UpdateStateMessage(RobustResourceStatus.Disconnected);
                Dispatcher.Invoke(new MethodDelegate(delegate()
            {
                _eventLogWriter.WriteToLog(System.Diagnostics.EventLogEntryType.Error, "The Kiosk has failed to connect to the fountain.\n" + ex.Message + "\n\nInner Exception\n" + ex.InnerException);
            }));
            }
        }


        /// <summary>
        /// Called by the constructor to handle the creation of the dots.
        /// </summary>
        private void CreateDots()
        {
            Action<Valve> AddValve = (Valve v) => { _valves.Add(v.Name, v); };

            AddValve(new Valve("V1", 355, 122));
            AddValve(new Valve("V2", 281, 162));
            AddValve(new Valve("V3", 221, 214));
            AddValve(new Valve("V4", 172, 279));
            AddValve(new Valve("V5", 139, 349));
            AddValve(new Valve("V6", 121, 419));
            AddValve(new Valve("V7", 121, 493));
            AddValve(new Valve("V8", 144, 560));
            AddValve(new Valve("V9", 179, 624));
            AddValve(new Valve("V10", 224, 681));

            AddValve(new Valve("HC", 491, 247));
            AddValve(new Valve("HR", 491, 292));

            AddValve(new Valve("H1", 736, 233));
            AddValve(new Valve("H2", 804, 268));
            AddValve(new Valve("H3", 853, 308));
            AddValve(new Valve("H4", 881, 346));
            AddValve(new Valve("H5", 896, 384));
            AddValve(new Valve("H6", 899, 417));
            AddValve(new Valve("H7", 887, 448));
            AddValve(new Valve("H8", 860, 473));
            AddValve(new Valve("H9", 816, 495));
            AddValve(new Valve("H10", 764, 512));

            AddValve(new Valve("VC", 614, 82));
            AddValve(new Valve("VR", 597, 147));

            foreach (Valve valve in _valves.Values)
            {
                FountainCanvas.Children.Add(valve.Dot);
                Canvas.SetLeft(valve.Dot, valve.X - Valve.DotSize / 2);
                Canvas.SetTop(valve.Dot, valve.Y - Valve.DotSize / 2);
            }
        }

        /// <summary>
        /// Reads in the security code needed to communicate with the server
        /// </summary>
        private void ReadCode()
        {
            try
            {
                StreamReader sr = new StreamReader(@"C:\FountainData\kioskcode.txt");
                Dispatcher.Invoke(new MethodDelegate(delegate()
            {
                _code = sr.ReadLine();
            }));
                sr.Close();
                sr.Dispose();

            }
            catch (Exception)
            {
                Dispatcher.Invoke(new MethodDelegate(delegate()
            {
                _eventLogWriter.WriteToLog(System.Diagnostics.EventLogEntryType.Error, @"Unable to read code file from C:\kioskcode.txt");
            }));
                UpdateStateMessage(RobustResourceStatus.CodeFileNotFound);
                Dispatcher.Invoke(new MethodDelegate(delegate()
            {
                _codeFileFound = false;
            }));
            }
        }

        /// <summary>
        /// Sends the restricted valves to the server. If this failes, it locks out user interaction
        /// </summary>
        private void SendRestrictedValves()
        {
            string foldername = @"C:\FountainData";
            try
            {
                StreamReader sr = new StreamReader(foldername + @"\RestrictedKioskValves.txt");

                List<string> restricted = new List<string>();
                while (!sr.EndOfStream)
                {
                    restricted.Add(sr.ReadLine());
                }

                // Send restricted valves
                try
                {
                    Dispatcher.Invoke(new MethodDelegate(delegate()
                    {
                        _connection.AddRestrictedValves(restricted, CommandPriority.kiosk, _code);
                    }));
                }
                catch (Exception)
                {
                    UpdateStateMessage(RobustResourceStatus.Disconnected);
                }
            }
            catch (IOException ex)
            {
                EventLogWriter wr = new EventLogWriter("Kiosk");
                wr.WriteToLog(System.Diagnostics.EventLogEntryType.Error, "The restricted valves were not able to be set. Disabling user input to prevent damage to valves.\n" + ex.Message + "\n Looking in: " + foldername);

                this.IsEnabled = false;
                UpdateStateMessage(RobustResourceStatus.RestrictedFileNotFound);
                Dispatcher.Invoke(new MethodDelegate(delegate()
                {
                    _restrictFileFound = false;
                }));
            }
        }

        /// <summary>
        /// Gets the current state from the fountain and update the view
        /// </summary>
        private void GetCurrentFountainState()
        {
            bool connectionLost = false;
            Dispatcher.Invoke(new MethodDelegate(delegate()
            {
                connectionLost = _connectionLost;
            }));

            if (!connectionLost)
            {
                List<string> serverValveList = new List<string>();
                int tryout = 0;
                bool success = false;
                while (!success)
                {
                    try
                    {
                        tryout++;
                        serverValveList = _connection.GetActiveValves();

                        //Dispatcher.Invoke(new MethodDelegate(delegate()
                        //{
                        //}));
                        //UpdateStateMessage(RobustResourceStatus.Idling);
                        success = true;
                    }
                    catch (Exception)
                    {
                        if (tryout > TRYOUT)
                        {
                            UpdateStateMessage(RobustResourceStatus.Disconnected);
                            success = true;
                        }
                    }
                }

                // get the newly activated valves
                foreach (string s in serverValveList)
                {
                    Fountain_ValveChanged(s, true);
                }

                Dispatcher.Invoke(new MethodDelegate(delegate()
                {
                    // disable valves not in the new state
                    foreach (string s in _activeValves)
                    {
                        if (!serverValveList.Contains(s))
                        {
                            Fountain_ValveChanged(s, false);
                        }
                    }

                    _activeValves = serverValveList;
                }));

                bool shouldSleep = false;
                Dispatcher.Invoke(new MethodDelegate(delegate()
                {
                    shouldSleep = _isUserInteraction;
                }));

                if (shouldSleep)
                {
                    Thread.Sleep(TIMEOUT);
                    ResetFountainValves();

                    try
                    {
                        Dispatcher.Invoke(new MethodDelegate(delegate()
                        {
                            _connection.ResetThreshold(CommandPriority.kiosk, _code);
                        }));
                        UpdateStateMessage(RobustResourceStatus.Idling);
                    }
                    catch (Exception)
                    {
                        UpdateStateMessage(RobustResourceStatus.Disconnected);
                    }
                }

                Dispatcher.Invoke(new MethodDelegate(delegate()
                {
                    _isUserInteraction = false;
                }));

                Thread t = new Thread(() => { GetCurrentFountainState(); });
                t.IsBackground = true;
                t.Start();
            }
        }

        /// <summary>
        /// Shuts off all of the valves on the fountain before restoring control to the lower priority 
        /// </summary>
        private void ResetFountainValves()
        {
            bool connectionLost = false;
            Dispatcher.Invoke(new MethodDelegate(delegate()
            {
                connectionLost = _connectionLost;
            }));

            if (!connectionLost)
            {
                try
                {
                    Dispatcher.Invoke(new MethodDelegate(delegate()
                    {
                        List<string> valves = _connection.GetActiveValves();
                        _connection.SendCommand("OFF", valves, CommandPriority.kiosk, _code);
                    }));
                }
                catch (Exception)
                {
                    UpdateStateMessage(RobustResourceStatus.Disconnected);
                }

                Dispatcher.Invoke(new MethodDelegate(delegate()
                {
                    foreach (string s in _activeValves)
                    {
                        Fountain_ValveChanged(s, false);
                    }
                }));

                Dispatcher.Invoke(new MethodDelegate(delegate()
                {
                    foreach (string s in _restrictedValves)
                    {
                        Fountain_ValveDisabled(s, false);
                    }
                }));

                Dispatcher.Invoke(new MethodDelegate(delegate()
                {
                    _activeValves.Clear();
                }));
            }
        }

        /// <summary>
        /// Updates the main screen when the status changes
        /// </summary>
        /// <param name="status"></param>
        void UpdateStateMessage(RobustResourceStatus status)
        {
            bool restrictFileFount = true;
            bool codeFileFound = true;
            Dispatcher.Invoke(new MethodDelegate(delegate()
            {
                restrictFileFount = _restrictFileFound;
            }));
            Dispatcher.Invoke(new MethodDelegate(delegate()
            {
                codeFileFound = _codeFileFound;
            }));

            if (!restrictFileFount || !codeFileFound)
                return;

            string message = "HELLO WORLD";
            switch (status)
            {
                case RobustResourceStatus.Disconnected:
                    message = "The kiosk has disconnected from the fountain. Come back later!";
                    Dispatcher.Invoke(new MethodDelegate(delegate()
                    {
                        //_connectionLost = true;
                    }));
                    break;

                case RobustResourceStatus.Idling:
                    message = "Tap any of the dots to control the fountain!"; 
                    break;

                case RobustResourceStatus.FountainOff:
                    message = "The fountain is currently offline";
                    break;

                case RobustResourceStatus.Preempted:
                    message = "Another system is controlling the fountain. Come back later!";
                    break;

                case RobustResourceStatus.Controlling:
                    message = "You're in control!";
                    break;

                case RobustResourceStatus.Waiting:
                    message = "Another kiosk user is using the fountain. You'll get your turn in a few seconds.";
                    break;

                case RobustResourceStatus.CodeFileNotFound:
                    message = "Unable to establish telepathic connection with fountain.\nCome back later!";
                    break;
                case RobustResourceStatus.RestrictedFileNotFound:
                    message = "Unable to find all the bits.\nCome back later!";
                    break;
                default:
                    throw new InvalidOperationException("The laws of physics no longer apply. You're free!");
            }

            this.Dispatcher.Invoke(DispatcherPriority.Normal, (Action)(() => { MessageText.Text = message; }));
        }

        private delegate void MethodDelegate();

        /// <summary>
        /// When the valve changes we need to update the dots
        /// </summary>
        /// <param name="valve"></param>
        /// <param name="newState"></param>
        void Fountain_ValveChanged(string valve, bool newState)
        {
            // We need to update our screen
            Dispatcher.Invoke(new MethodDelegate(delegate()
            {
                if (_valves.ContainsKey(valve))
                    _valves[valve].State = newState;
            }));
        }

        /// <summary>
        /// Hides a dot when the valve is disabled
        /// </summary>
        /// <param name="valve"></param>
        /// <param name="isDisabled"></param>
        void Fountain_ValveDisabled(string valve, bool isDisabled)
        {
            Dispatcher.Invoke(new MethodDelegate(delegate()
            {
                if (_valves.ContainsKey(valve))
                    _valves[valve].Disabled = isDisabled;
            }));
        }

        /// <summary>
        /// Gets the restricted valves so it knows which dot to hide
        /// </summary>
        private void GetRestrictedValves()
        {
            try
            {
                Dispatcher.Invoke(new MethodDelegate(delegate()
                {
                    _restrictedValves = _connection.GetRestrictedValves(CommandPriority.kiosk);
                }));
            }
            catch (Exception)
            {
                UpdateStateMessage(RobustResourceStatus.Disconnected);
            }

            Dispatcher.Invoke(new MethodDelegate(delegate()
            {
                foreach (string s in _restrictedValves)
                {
                    Fountain_ValveDisabled(s, true);
                }
            }));
        }


        /// <summary>
        /// When the user presses down with their finger (i.e. mouse) the valve should be toggled
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void FountainControl_MouseDown(object sender, MouseButtonEventArgs e)
        {
            bool connectionLost = false;
            Dispatcher.Invoke(new MethodDelegate(delegate()
            {
                connectionLost = _connectionLost;
            }));

            if (!connectionLost)
            {
                try
                {
                    Valve v = GetValveFromPoint(e);
                    if (v != null)
                    {
                        List<string> cmd = new List<string>();
                        cmd.Add(v.Name);
                        string state = !v.State ? "ON" : "OFF";

                        bool validChange = false; // checks to make sure that the command was successfully sent
                        try
                        {
                            Dispatcher.Invoke(new MethodDelegate(delegate()
                            {
                                validChange = _connection.SendCommand(state, cmd, CommandPriority.kiosk, _code);
                            }));
                            if (validChange)
                            {
                                GetRestrictedValves();
                                Dispatcher.Invoke(new MethodDelegate(delegate()
                                {
                                    _isUserInteraction = true;
                                }));
                                UpdateStateMessage(RobustResourceStatus.Controlling);
                                Fountain_ValveChanged(v.Name, !v.State);
                                Dispatcher.Invoke(new MethodDelegate(delegate()
                                {
                                    _activeValves.Add(v.Name);
                                }));
                            }
                            else
                            {
                                UpdateStateMessage(RobustResourceStatus.Preempted);
                                Thread.Sleep(10000);
                                UpdateStateMessage(RobustResourceStatus.Idling);
                            }
                        }
                        catch (Exception)
                        {
                            UpdateStateMessage(RobustResourceStatus.Disconnected);
                        }
                    }
                }
                catch (Exception)
                {
                    UpdateStateMessage(RobustResourceStatus.Disconnected);
                }
            }
        }

        /// <summary>
        /// Find the valve that is closest to where the mouse is
        /// </summary>
        /// <param name="e"></param>
        /// <returns></returns>
        private Valve GetValveFromPoint(MouseEventArgs e)
        {
            Point point = e.GetPosition(FountainCanvas);

            // Let's find the closest point to our tap
            Valve valve = _valves.Values.OrderBy(v => v.GetDistanceFromPointSquared(point)).First();

            // Have a minimum closeness
            if (valve.GetDistanceFromPointSquared(point) < Math.Pow(Valve.DotSize + 20, 2))
                return valve;
            else
                return null;
        }
    }

    public enum RobustResourceStatus
    {
        /// <summary>
        /// The robust connection is not currently connected to the server.
        /// </summary>
        Disconnected,

        /// <summary>
        /// The robust connection is connected, but there is no request for the given resource.
        /// </summary>
        Idling,

        /// <summary>
        /// The robust connection is controlling the resource.
        /// </summary>
        Controlling,

        /// <summary>
        /// Another higher-priority user is controlling the resource.
        /// </summary>
        Preempted,

        /// <summary>
        /// The robust connection is waiting for another user of the same priority to finish.
        /// </summary>
        Waiting,

        /// <summary>
        /// The fountain is off, and so the resource is not available.
        /// </summary>
        FountainOff,

        /// <summary>
        /// Message to display if the restricted valve file is not found
        /// </summary>
        RestrictedFileNotFound,

        /// <summary>
        /// The code file is not found
        /// </summary>
        CodeFileNotFound
    }
}
